// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package assert

import (
	"errors"
	"testing"

	"github.com/IBM/fp-go/v2/optics/prism"
	"github.com/IBM/fp-go/v2/option"
	"github.com/IBM/fp-go/v2/result"
)

func TestEqual(t *testing.T) {
	t.Run("should pass when values are equal", func(t *testing.T) {
		result := Equal(42)(42)(t)
		if !result {
			t.Error("Expected Equal to pass for equal values")
		}
	})

	t.Run("should fail when values are not equal", func(t *testing.T) {
		mockT := &testing.T{}
		result := Equal(42)(43)(mockT)
		if result {
			t.Error("Expected Equal to fail for different values")
		}
	})

	t.Run("should work with strings", func(t *testing.T) {
		result := Equal("hello")("hello")(t)
		if !result {
			t.Error("Expected Equal to pass for equal strings")
		}
	})
}

func TestNotEqual(t *testing.T) {
	t.Run("should pass when values are not equal", func(t *testing.T) {
		result := NotEqual(42)(43)(t)
		if !result {
			t.Error("Expected NotEqual to pass for different values")
		}
	})

	t.Run("should fail when values are equal", func(t *testing.T) {
		mockT := &testing.T{}
		result := NotEqual(42)(42)(mockT)
		if result {
			t.Error("Expected NotEqual to fail for equal values")
		}
	})
}

func TestArrayNotEmpty(t *testing.T) {
	t.Run("should pass for non-empty array", func(t *testing.T) {
		arr := []int{1, 2, 3}
		result := ArrayNotEmpty(arr)(t)
		if !result {
			t.Error("Expected ArrayNotEmpty to pass for non-empty array")
		}
	})

	t.Run("should fail for empty array", func(t *testing.T) {
		mockT := &testing.T{}
		arr := []int{}
		result := ArrayNotEmpty(arr)(mockT)
		if result {
			t.Error("Expected ArrayNotEmpty to fail for empty array")
		}
	})
}

func TestRecordNotEmpty(t *testing.T) {
	t.Run("should pass for non-empty map", func(t *testing.T) {
		mp := map[string]int{"a": 1, "b": 2}
		result := RecordNotEmpty(mp)(t)
		if !result {
			t.Error("Expected RecordNotEmpty to pass for non-empty map")
		}
	})

	t.Run("should fail for empty map", func(t *testing.T) {
		mockT := &testing.T{}
		mp := map[string]int{}
		result := RecordNotEmpty(mp)(mockT)
		if result {
			t.Error("Expected RecordNotEmpty to fail for empty map")
		}
	})
}

func TestArrayLength(t *testing.T) {
	t.Run("should pass when length matches", func(t *testing.T) {
		arr := []int{1, 2, 3}
		result := ArrayLength[int](3)(arr)(t)
		if !result {
			t.Error("Expected ArrayLength to pass when length matches")
		}
	})

	t.Run("should fail when length doesn't match", func(t *testing.T) {
		mockT := &testing.T{}
		arr := []int{1, 2, 3}
		result := ArrayLength[int](5)(arr)(mockT)
		if result {
			t.Error("Expected ArrayLength to fail when length doesn't match")
		}
	})

	t.Run("should work with empty array", func(t *testing.T) {
		arr := []string{}
		result := ArrayLength[string](0)(arr)(t)
		if !result {
			t.Error("Expected ArrayLength to pass for empty array with expected length 0")
		}
	})
}

func TestRecordLength(t *testing.T) {
	t.Run("should pass when map length matches", func(t *testing.T) {
		mp := map[string]int{"a": 1, "b": 2}
		result := RecordLength[string, int](2)(mp)(t)
		if !result {
			t.Error("Expected RecordLength to pass when length matches")
		}
	})

	t.Run("should fail when map length doesn't match", func(t *testing.T) {
		mockT := &testing.T{}
		mp := map[string]int{"a": 1}
		result := RecordLength[string, int](3)(mp)(mockT)
		if result {
			t.Error("Expected RecordLength to fail when length doesn't match")
		}
	})
}

func TestStringLength(t *testing.T) {
	t.Run("should pass when string length matches", func(t *testing.T) {
		str := "hello"
		result := StringLength[string, int](5)(str)(t)
		if !result {
			t.Error("Expected StringLength to pass when length matches")
		}
	})

	t.Run("should fail when string length doesn't match", func(t *testing.T) {
		mockT := &testing.T{}
		str := "hello"
		result := StringLength[string, int](10)(str)(mockT)
		if result {
			t.Error("Expected StringLength to fail when length doesn't match")
		}
	})

	t.Run("should work with empty string", func(t *testing.T) {
		str := ""
		result := StringLength[string, int](0)(str)(t)
		if !result {
			t.Error("Expected StringLength to pass for empty string with expected length 0")
		}
	})
}

func TestNoError(t *testing.T) {
	t.Run("should pass when error is nil", func(t *testing.T) {
		result := NoError(nil)(t)
		if !result {
			t.Error("Expected NoError to pass when error is nil")
		}
	})

	t.Run("should fail when error is not nil", func(t *testing.T) {
		mockT := &testing.T{}
		err := errors.New("test error")
		result := NoError(err)(mockT)
		if result {
			t.Error("Expected NoError to fail when error is not nil")
		}
	})
}

func TestError(t *testing.T) {
	t.Run("should pass when error is not nil", func(t *testing.T) {
		err := errors.New("test error")
		result := Error(err)(t)
		if !result {
			t.Error("Expected Error to pass when error is not nil")
		}
	})

	t.Run("should fail when error is nil", func(t *testing.T) {
		mockT := &testing.T{}
		result := Error(nil)(mockT)
		if result {
			t.Error("Expected Error to fail when error is nil")
		}
	})
}

func TestSuccess(t *testing.T) {
	t.Run("should pass for successful result", func(t *testing.T) {
		res := result.Of(42)
		result := Success(res)(t)
		if !result {
			t.Error("Expected Success to pass for successful result")
		}
	})

	t.Run("should fail for error result", func(t *testing.T) {
		mockT := &testing.T{}
		res := result.Left[int](errors.New("test error"))
		result := Success(res)(mockT)
		if result {
			t.Error("Expected Success to fail for error result")
		}
	})
}

func TestFailure(t *testing.T) {
	t.Run("should pass for error result", func(t *testing.T) {
		res := result.Left[int](errors.New("test error"))
		result := Failure(res)(t)
		if !result {
			t.Error("Expected Failure to pass for error result")
		}
	})

	t.Run("should fail for successful result", func(t *testing.T) {
		mockT := &testing.T{}
		res := result.Of(42)
		result := Failure(res)(mockT)
		if result {
			t.Error("Expected Failure to fail for successful result")
		}
	})
}

func TestArrayContains(t *testing.T) {
	t.Run("should pass when element is in array", func(t *testing.T) {
		arr := []int{1, 2, 3, 4, 5}
		result := ArrayContains(3)(arr)(t)
		if !result {
			t.Error("Expected ArrayContains to pass when element is in array")
		}
	})

	t.Run("should fail when element is not in array", func(t *testing.T) {
		mockT := &testing.T{}
		arr := []int{1, 2, 3}
		result := ArrayContains(10)(arr)(mockT)
		if result {
			t.Error("Expected ArrayContains to fail when element is not in array")
		}
	})

	t.Run("should work with strings", func(t *testing.T) {
		arr := []string{"apple", "banana", "cherry"}
		result := ArrayContains("banana")(arr)(t)
		if !result {
			t.Error("Expected ArrayContains to pass for string element")
		}
	})
}

func TestContainsKey(t *testing.T) {
	t.Run("should pass when key exists in map", func(t *testing.T) {
		mp := map[string]int{"a": 1, "b": 2, "c": 3}
		result := ContainsKey[int]("b")(mp)(t)
		if !result {
			t.Error("Expected ContainsKey to pass when key exists")
		}
	})

	t.Run("should fail when key doesn't exist in map", func(t *testing.T) {
		mockT := &testing.T{}
		mp := map[string]int{"a": 1, "b": 2}
		result := ContainsKey[int]("z")(mp)(mockT)
		if result {
			t.Error("Expected ContainsKey to fail when key doesn't exist")
		}
	})
}

func TestNotContainsKey(t *testing.T) {
	t.Run("should pass when key doesn't exist in map", func(t *testing.T) {
		mp := map[string]int{"a": 1, "b": 2}
		result := NotContainsKey[int]("z")(mp)(t)
		if !result {
			t.Error("Expected NotContainsKey to pass when key doesn't exist")
		}
	})

	t.Run("should fail when key exists in map", func(t *testing.T) {
		mockT := &testing.T{}
		mp := map[string]int{"a": 1, "b": 2}
		result := NotContainsKey[int]("a")(mp)(mockT)
		if result {
			t.Error("Expected NotContainsKey to fail when key exists")
		}
	})
}

func TestThat(t *testing.T) {
	t.Run("should pass when predicate is true", func(t *testing.T) {
		isEven := func(n int) bool { return n%2 == 0 }
		result := That(isEven)(42)(t)
		if !result {
			t.Error("Expected That to pass when predicate is true")
		}
	})

	t.Run("should fail when predicate is false", func(t *testing.T) {
		mockT := &testing.T{}
		isEven := func(n int) bool { return n%2 == 0 }
		result := That(isEven)(43)(mockT)
		if result {
			t.Error("Expected That to fail when predicate is false")
		}
	})

	t.Run("should work with string predicates", func(t *testing.T) {
		startsWithH := func(s string) bool { return len(s) > 0 && s[0] == 'h' }
		result := That(startsWithH)("hello")(t)
		if !result {
			t.Error("Expected That to pass for string predicate")
		}
	})
}

func TestAllOf(t *testing.T) {
	t.Run("should pass when all assertions pass", func(t *testing.T) {
		assertions := AllOf([]Reader{
			Equal(42)(42),
			Equal("hello")("hello"),
			ArrayNotEmpty([]int{1, 2, 3}),
		})
		result := assertions(t)
		if !result {
			t.Error("Expected AllOf to pass when all assertions pass")
		}
	})

	t.Run("should fail when any assertion fails", func(t *testing.T) {
		mockT := &testing.T{}
		assertions := AllOf([]Reader{
			Equal(42)(42),
			Equal("hello")("goodbye"),
			ArrayNotEmpty([]int{1, 2, 3}),
		})
		result := assertions(mockT)
		if result {
			t.Error("Expected AllOf to fail when any assertion fails")
		}
	})

	t.Run("should work with empty array", func(t *testing.T) {
		assertions := AllOf([]Reader{})
		result := assertions(t)
		if !result {
			t.Error("Expected AllOf to pass for empty array")
		}
	})

	t.Run("should combine multiple array assertions", func(t *testing.T) {
		arr := []int{1, 2, 3, 4, 5}
		assertions := AllOf([]Reader{
			ArrayNotEmpty(arr),
			ArrayLength[int](5)(arr),
			ArrayContains(3)(arr),
		})
		result := assertions(t)
		if !result {
			t.Error("Expected AllOf to pass for multiple array assertions")
		}
	})
}

func TestRunAll(t *testing.T) {
	t.Run("should run all named test cases", func(t *testing.T) {
		testcases := map[string]Reader{
			"equality":     Equal(42)(42),
			"string_check": Equal("test")("test"),
			"array_check":  ArrayNotEmpty([]int{1, 2, 3}),
		}
		result := RunAll(testcases)(t)
		if !result {
			t.Error("Expected RunAll to pass when all test cases pass")
		}
	})

	// Note: Testing failure behavior of RunAll is tricky because subtests
	// will actually fail in the test framework. The function works correctly
	// as demonstrated by the passing test above.

	t.Run("should work with empty test cases", func(t *testing.T) {
		testcases := map[string]Reader{}
		result := RunAll(testcases)(t)
		if !result {
			t.Error("Expected RunAll to pass for empty test cases")
		}
	})
}

func TestEq(t *testing.T) {
	t.Run("should return true for equal values", func(t *testing.T) {
		if !Eq.Equals(42, 42) {
			t.Error("Expected Eq to return true for equal integers")
		}
	})

	t.Run("should return false for different values", func(t *testing.T) {
		if Eq.Equals(42, 43) {
			t.Error("Expected Eq to return false for different integers")
		}
	})

	t.Run("should work with strings", func(t *testing.T) {
		if !Eq.Equals("hello", "hello") {
			t.Error("Expected Eq to return true for equal strings")
		}
		if Eq.Equals("hello", "world") {
			t.Error("Expected Eq to return false for different strings")
		}
	})

	t.Run("should work with slices", func(t *testing.T) {
		arr1 := []int{1, 2, 3}
		arr2 := []int{1, 2, 3}
		if !Eq.Equals(arr1, arr2) {
			t.Error("Expected Eq to return true for equal slices")
		}
	})
}

func TestLocal(t *testing.T) {
	type User struct {
		Name string
		Age  int
	}

	t.Run("should focus assertion on a property", func(t *testing.T) {
		// Create an assertion that checks if age is positive
		ageIsPositive := That(func(age int) bool { return age > 0 })

		// Focus this assertion on the Age field of User
		userAgeIsPositive := Local(func(u User) int { return u.Age })(ageIsPositive)

		// Test with a user who has a positive age
		user := User{Name: "Alice", Age: 30}
		result := userAgeIsPositive(user)(t)
		if !result {
			t.Error("Expected focused assertion to pass for positive age")
		}
	})

	t.Run("should fail when focused property doesn't match", func(t *testing.T) {
		mockT := &testing.T{}
		ageIsPositive := That(func(age int) bool { return age > 0 })
		userAgeIsPositive := Local(func(u User) int { return u.Age })(ageIsPositive)

		// Test with a user who has zero age
		user := User{Name: "Bob", Age: 0}
		result := userAgeIsPositive(user)(mockT)
		if result {
			t.Error("Expected focused assertion to fail for zero age")
		}
	})

	t.Run("should compose with other assertions", func(t *testing.T) {
		// Create multiple focused assertions
		nameNotEmpty := Local(func(u User) string { return u.Name })(
			That(func(name string) bool { return len(name) > 0 }),
		)
		ageInRange := Local(func(u User) int { return u.Age })(
			That(func(age int) bool { return age >= 18 && age <= 100 }),
		)

		user := User{Name: "Charlie", Age: 25}
		assertions := AllOf([]Reader{
			nameNotEmpty(user),
			ageInRange(user),
		})

		result := assertions(t)
		if !result {
			t.Error("Expected composed focused assertions to pass")
		}
	})

	t.Run("should work with Equal assertion", func(t *testing.T) {
		// Focus Equal assertion on Name field
		nameIsAlice := Local(func(u User) string { return u.Name })(Equal("Alice"))

		user := User{Name: "Alice", Age: 30}
		result := nameIsAlice(user)(t)
		if !result {
			t.Error("Expected focused Equal assertion to pass")
		}
	})
}

func TestLocalL(t *testing.T) {
	// Note: LocalL requires lens package which provides lens operations.
	// This test demonstrates the concept, but actual usage would require
	// proper lens definitions.

	t.Run("conceptual test for LocalL", func(t *testing.T) {
		// LocalL is similar to Local but uses lenses for focusing.
		// It would be used like:
		// validEmail := That(func(email string) bool { return strings.Contains(email, "@") })
		// validPersonEmail := LocalL(emailLens)(validEmail)
		//
		// The actual implementation would require lens definitions from the lens package.
		// This test serves as documentation for the intended usage.
	})
}

func TestFromOptional(t *testing.T) {
	type DatabaseConfig struct {
		Host string
		Port int
	}

	type Config struct {
		Database *DatabaseConfig
	}

	// Create an Optional that focuses on the Database field
	dbOptional := Optional[Config, *DatabaseConfig]{
		GetOption: func(c Config) option.Option[*DatabaseConfig] {
			if c.Database != nil {
				return option.Of(c.Database)
			}
			return option.None[*DatabaseConfig]()
		},
		Set: func(db *DatabaseConfig) func(Config) Config {
			return func(c Config) Config {
				c.Database = db
				return c
			}
		},
	}

	t.Run("should pass when optional value is present", func(t *testing.T) {
		config := Config{Database: &DatabaseConfig{Host: "localhost", Port: 5432}}
		hasDatabaseConfig := FromOptional(dbOptional)
		result := hasDatabaseConfig(config)(t)
		if !result {
			t.Error("Expected FromOptional to pass when optional value is present")
		}
	})

	t.Run("should fail when optional value is absent", func(t *testing.T) {
		mockT := &testing.T{}
		emptyConfig := Config{Database: nil}
		hasDatabaseConfig := FromOptional(dbOptional)
		result := hasDatabaseConfig(emptyConfig)(mockT)
		if result {
			t.Error("Expected FromOptional to fail when optional value is absent")
		}
	})

	t.Run("should work with nested optionals", func(t *testing.T) {
		type AdvancedSettings struct {
			Cache bool
		}

		type Settings struct {
			Advanced *AdvancedSettings
		}

		advancedOptional := Optional[Settings, *AdvancedSettings]{
			GetOption: func(s Settings) option.Option[*AdvancedSettings] {
				if s.Advanced != nil {
					return option.Of(s.Advanced)
				}
				return option.None[*AdvancedSettings]()
			},
			Set: func(adv *AdvancedSettings) func(Settings) Settings {
				return func(s Settings) Settings {
					s.Advanced = adv
					return s
				}
			},
		}

		settings := Settings{Advanced: &AdvancedSettings{Cache: true}}
		hasAdvanced := FromOptional(advancedOptional)
		result := hasAdvanced(settings)(t)
		if !result {
			t.Error("Expected FromOptional to pass for nested optional")
		}
	})
}

// Helper types for Prism testing
type PrismTestResult interface {
	isPrismTestResult()
}

type PrismTestSuccess struct {
	Value int
}

type PrismTestFailure struct {
	Error string
}

func (PrismTestSuccess) isPrismTestResult() {}
func (PrismTestFailure) isPrismTestResult() {}

func TestFromPrism(t *testing.T) {
	// Create a Prism that focuses on Success variant using prism.MakePrism
	successPrism := prism.MakePrism(
		func(r PrismTestResult) option.Option[int] {
			if s, ok := r.(PrismTestSuccess); ok {
				return option.Of(s.Value)
			}
			return option.None[int]()
		},
		func(v int) PrismTestResult {
			return PrismTestSuccess{Value: v}
		},
	)

	// Create a Prism that focuses on Failure variant
	failurePrism := prism.MakePrism(
		func(r PrismTestResult) option.Option[string] {
			if f, ok := r.(PrismTestFailure); ok {
				return option.Of(f.Error)
			}
			return option.None[string]()
		},
		func(err string) PrismTestResult {
			return PrismTestFailure{Error: err}
		},
	)

	t.Run("should pass when prism successfully extracts", func(t *testing.T) {
		result := PrismTestSuccess{Value: 42}
		isSuccess := FromPrism(successPrism)
		testResult := isSuccess(result)(t)
		if !testResult {
			t.Error("Expected FromPrism to pass when prism extracts successfully")
		}
	})

	t.Run("should fail when prism cannot extract", func(t *testing.T) {
		mockT := &testing.T{}
		result := PrismTestFailure{Error: "something went wrong"}
		isSuccess := FromPrism(successPrism)
		testResult := isSuccess(result)(mockT)
		if testResult {
			t.Error("Expected FromPrism to fail when prism cannot extract")
		}
	})

	t.Run("should work with failure prism", func(t *testing.T) {
		result := PrismTestFailure{Error: "test error"}
		isFailure := FromPrism(failurePrism)
		testResult := isFailure(result)(t)
		if !testResult {
			t.Error("Expected FromPrism to pass for failure prism on failure result")
		}
	})

	t.Run("should fail with failure prism on success result", func(t *testing.T) {
		mockT := &testing.T{}
		result := PrismTestSuccess{Value: 100}
		isFailure := FromPrism(failurePrism)
		testResult := isFailure(result)(mockT)
		if testResult {
			t.Error("Expected FromPrism to fail for failure prism on success result")
		}
	})
}
